"""
Author: YuanLu
Date: 2023-12-12 08:08:58
LastEditTime: 2023-12-23 10:14:56
LastEditors: Please set LastEditors
Description: socketserver包实现多线程UDP服务器根据COAP协议实现BC95-B5连接电信AEP功能
    实现COAP相关设备注册,设备订阅功能《最终实现设备能够发送数据,类似电信AEP功能    
FilePath: udpCoapServer.py
"""
# !/usr/bin/python3
# -*- coding: UTF-8 -*-
# 文件名：udpCoapServer.py
# socket底层代码实现多线程TCP服务

# import base64
# import datetime
# import json
# import sys

import logging  # 日志库
import os
import random
import socketserver  # 多线程版本TCP服务器
import sqlite3
import struct
import threading  # 导入线程模块
import time  # 时间函数库
from binascii import a2b_hex, b2a_hex
from logging import handlers

import pandas as pd

# import aiocoap

thName = ""  # 线程名称
thIdent = ""  # 线程ID

CON_TYPE = 'CON'  # 0:Confirmable
NON_TYPE = 'NON'  # 1:Non-confirmable
ACK_TYPE = 'ACK'  # 2:Acknowledgement
RST_TYPE = 'RST'  # 3:Reset

METHOD_EMPTY = 'EMPTY'  # 0:EMPTY
METHOD_GET = 'GET'  # 1:GET
METHOD_POST = 'POST'  # 2:POST
METHOD_PUT = 'PUT'  # 3:PUT
METHOD_DELETE = 'DELETE'  # 4:DELETE

# 创建日志实例
class Logger(object):
    level_relations = {"debug": logging.DEBUG, "info": logging.INFO, "warning": logging.WARNING, "error": logging.ERROR, "crit": logging.CRITICAL}  # 日志关系映射

    def __init__(self, filename, level="info", backCount=500, fmt="%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s"):
        self.logger = logging.getLogger(filename)
        format_str = logging.Formatter(fmt)  # 设置日志格式
        self.logger.setLevel(self.level_relations.get(level))  # 设置日志级别

        sh = logging.StreamHandler()  # 往屏幕上输出
        sh.setFormatter(format_str)  # 设置屏幕上显示的格式
        self.logger.addHandler(sh)  # 把对象加到logger里

        fh = handlers.RotatingFileHandler(filename=filename, maxBytes=10*2*1024*1024, backupCount=backCount, encoding="utf-8")  # 按照文件大小分割日志文件
        fh.setLevel(self.level_relations.get(level))
        fh.setFormatter(format_str)  # 设置文件里写入的格式
        self.logger.addHandler(fh)

def sqliteHandle(dbname, dbData, imei):
    IMEI = imei 
    dbDir = './dbDir/'
    if not os.path.exists(dbDir):
        os.mkdir(dbDir)
    
    dbname = dbDir + imei + '.db'   
    if not os.path.exists(dbname): # 如果db文件不存在则创建
        conn = sqlite3.connect(dbname)# 创建连接，新建或打开数据库
        cur = conn.cursor()           # 创建游标对象
    
        # ID           INT       PRIMARY KEY     NOT NULL,
        sql = '''CREATE TABLE FLOWDATA
                (
                IMEI           TEXT                     NOT NULL,
                GPRSID         TEXT                     NOT NULL,
                DateTime       TEXT                     NOT NULL,
                PosTotal       TEXT                     NOT NULL,
                NegTotal       TEXT                     NOT NULL,
                InsFlow        TEXT                     NOT NULL,
                InsPress       TEXT                     NOT NULL,                    
                InsertTime     Datetime                 NOT NULL
                );
                '''
        # 执行sql语句，编写sql语句很容易出错，因此采取异常处理
        try:
            cur.execute(sql) # 执行sql语句
            print('创建表成功！')
        except Exception as err:
            print(err)
            print('创建表失败！')
        finally:
            cur.close()  # 关闭游标
            conn.close()  # 关闭连接
            
    #连接数据库，没有会自动创建文件，数据结构还是要上面定义
    conn = sqlite3.connect(dbname)
    cur = conn.cursor()
    
    # 2、向表中插入数据
    # 在调用时再给出具体值，这样处理不仅灵活，还可以防止sql注入
    try:
        # cur.executemany(sql,[('李四',24),('王五',22),('赵六',25)])  # 插入多行数据
        
        # FLOWDATA表--数据库表
        sql = 'insert into FLOWDATA(IMEI,GPRSID,DateTime,PosTotal,NegTotal,InsFlow,InsPress,InsertTime)'
        sql += ' values (?,?, ?, ?, ?, ?, ?, ?)'
        # sql += ' values (?,?, ?, ?, ?, ?, ?)'
        
        # 插入传入的数据--示例1
        for index,value in enumerate(dbData):
            insTime = time.strftime("%Y-%m-%d %H:%M:%S")
            data = value.split(',')
            gprsid = data[0]
            dateTime = data[1]
            posTotal = data[2]
            negTotal = data[3]
            insFlow = data[4]
            insPress = data[5]
            # insSpeed = data[6]
            insertTime = insTime
            
            data = (IMEI,gprsid, dateTime, posTotal, negTotal, insFlow, insPress, insertTime)
            cur.execute(sql, data)  # 插入一行数据
            conn.commit()  # 提交事务，向表中写入
            # print('{:>3d} 插入数据成功'.format(index))
        
            # data = ('41004001', '{}'.format('2023-02-06 20:42:00'),'123.56','12.35', '125.65', '3.5', '2.3', '{}'.format(insTime))
            # print((data))
                        
        # 插入制定日期的间隔数据--示例2
        # readDate = '2023-01-31 00:00:00'
        # readDateList = DataConverter.getPandasDateList(readDate, 288*10, '5min')
        # for index,value in enumerate(readDateList):
        #     insTime = time.strftime("%Y-%m-%d %H:%M:%S")
        #     data = ('41004001', '{}'.format(value),'123.56','12.35', '125.65', '3.5', '2.3', '{}'.format(insTime))
        #     cur.execute(sql, data)  # 插入一行数据
        #     conn.commit()  # 提交事务，向表中写入
        #     print('{:>3d} 插入数据成功'.format(index))

            # meterNo表
            # sql = 'insert into meterNo(MeterNo,readTime,posTotal,negTotal,insFlow, inserTime)'
            # sql += ' values (?, ?, ?, ?, ?, ?)'
            # insTime = time.strftime("%Y-%m-%d %H:%M:%S")
            # data = ('41004001', '{}'.format(value),'1223.56','102.35', '125.65', '{}'.format(insTime))
            
            # 方法2
            # insert_sql = "insert into FLOWDATA values ("
            # insert_sql += ""
            # insert_sql += ")"
            # print(insert_sql)
            # cur.execute(insert_sql)
            # conn.commit()
    except Exception as err:
        # print(err)
        # print('插入数据失败,写入IMEI号的文件!')
        log.logger.info("-->插入数据失败,已写入[./dbDir/%s.txt]文件", imei)
        savetxt = dbDir + imei + '.txt'
        with open(savetxt, 'a+') as fp:
            for index,value in enumerate(dbData):
                fp.write(value + '\r\n')            
        
        conn.rollback()  # 事务回滚，返回到执行sql语句前的状态
    finally:
        cur.close()
        conn.close() 
            
def ShortToDec(data, mode): # 2字节有符号的16进制数转换成10进制
    # 例如：64FE有符号2字节的数据，转换成10进制数为：-412
    byte1 = bytearray()
    byte1.append(int(data[2:],16))
    byte1.append(int(data[:2],16))
        
    if mode == 'B' or mode == 'b': # 大端模式 
        data = struct.unpack("<h",byte1)[0] 
    else:             
        data = struct.unpack(">h",byte1)[0]  
    return data

def hightolow(strVal,flag):
    # 高低字节反转
    list1 = []
    for i in range(0, len(strVal), 2):
        list1.append(strVal[i : i + 2])
    list1.reverse() # 数据反转    
    
    if flag == 1:# 高低位反转后16进制转换成10进制
        strVal = int(''.join(list1),16)
        len1 = len(str(strVal))
        if (len1 < 6):
            strVal = '0' * (6 - len1) + str(strVal)
            
    elif flag == 2: # 高低位反转后转换成float类型
        strVal = ''.join(list1)
        strVal = struct.unpack('!f', bytes.fromhex(strVal))[0]
        strVal = round(strVal,4)
        len1 = len(str(strVal))
        if len1 <= 8:
            strVal = str(strVal) + '0' * (8 - len1)
            
    elif flag == 4: # 大端模式，高字节在前成float类型
        list1.reverse() # 传入的数据反转回来
        strVal = ''.join(list1)
        strVal = struct.unpack('!f', bytes.fromhex(strVal))[0]
        strVal = round(strVal,4)
        len1 = len(str(strVal))
        if len1 <= 8:
            strVal = str(strVal) + '0' * (8 - len1)
                            
    elif flag == 6: # 高低位反转后转换成double类型
        strVal = ''.join(list1)
        strVal = struct.unpack('!d', bytes.fromhex(strVal))[0]
        strVal = round(strVal,10)
        len1 = len(str(strVal))
        if len1 <= 16:
            strVal = str(strVal) + '0' * (16 - len1)

    elif flag == 61: # 大端模式，转后转换成double类型
        # strVal = ''.join(list1)
        # !代表是网络字节，低字节在左侧，高字节在右侧
        strVal = struct.unpack('!d', bytes.fromhex(strVal))[0]
        strVal = round(strVal,10)
        len1 = len(str(strVal))
        if len1 <= 16:
            strVal = str(strVal) + '0' * (16 - len1)
                                    
    elif flag == 3: # 把输入的字符串按字节高低位反转
        strVal = ''.join(list1)
        # print(strVal)
    
    elif flag == 5: # 高低位反转返回16进制的10进制数--2字节有符号数据处理
        strVal = int(''.join(list1),16)
        if (strVal < 0x7FFF):
            strVal = strVal
        else:
            strVal = (strVal - 0x10000)  # 返回有符号的10进制数

    else: # 高低位反转返回16进制的10进制数--4字节有符号数据处理
        # 高字节在前，低字节在后
        strVal = int(''.join(list1),16)
        if (strVal < 0x7FFFFFFF):
            strVal = strVal
        else:
            strVal = (strVal - 0x100000000)# 返回有符号的10进制数
    return strVal 

def getPandasDateList(startDate, dataNum, freq):
    # 指定开始时间和时间序列数量
    # startDate:起始时间，格式：'2019-01-03 00:00:00'
    # dataNum：从起始时间生成多少个日期数据
    # freq：每个数据之间相差多少分钟
    # 传入参数示例：'2022-12-10 00:00:00',index,'5min'
    datelist = pd.date_range(start=startDate, periods=dataNum, freq=freq)
    return datelist
            
def BaoTouShuiwuProto(frameData):
    vendorCode = frameData[20:20+4] # 厂商代码
    cmdFlg = frameData[38:38+2]
    meterNum = frameData[4:4+16]
    if cmdFlg == '02': # 功能码：数据上发
        tag3Status = frameData[64:184][6:]
        # print(tag3Status)
        startTime = tag3Status[6:6 + 12]
        runDay = int(tag3Status[18+6:24+4],16)            
        csq = int(tag3Status[28+6:34+2],16) 
        rsrp = ShortToDec(tag3Status[36+6:42+4], 'H')
        snr = ShortToDec(tag3Status[46+6:52+4], 'H')
        ecl = tag3Status[56+6:62+2]
        netStatus = tag3Status[64+6:70+2]            
        batVol =  int(tag3Status[72+6:78+4],16)/100
        outstr3 = '启动时间:{},'.format(startTime)
        outstr3 += '运行时常:{}/天,'.format(runDay)
        outstr3 += '信号质量:{},'.format(csq)
        outstr3 += '信号强度:{},'.format(rsrp)
        outstr3 += '信噪比:{},'.format(snr)
        outstr3 += '电池电压:{}/V,'.format(batVol)
        
        # print(outstr3)
        rtuData = frameData[184:-10]
        # print(rtuData)
        dataHead = rtuData[6:6+12+6+2] # 包含起始时间，数据记录间隔，数据组数
        timestr = dataHead[:12] # 起始时间 
        dataHead = dataHead[12:]   
        readFlg = int(dataHead[2:2+4],16) # 抄表间隔 
        dataNum = int(dataHead[6:8],16) # 数据个数
        # print(timestr)
                        
        strtime = '20' + timestr[:2] + '-' + timestr[2:4] + '-' + timestr[4:6] + ' '
        strtime += timestr[6:8] + ':' + timestr[8:10] + ':' + timestr[10:]
        # print(strtime, readFlg, dataNum)
        
        rtuData = rtuData[26:]

        startindex = 0
        endindex = 4+dataNum*8
        insflowData = rtuData[startindex:endindex][4:] # 瞬时流量，float
        # print(insflowData)
        
        startindex = endindex
        endindex += 4+dataNum*8
        postotalInt = rtuData[startindex:endindex][4:] # 正累计，4字节
        # print(postotalInt)
        
        startindex = endindex
        endindex += 4+dataNum*8
        postotalDec = rtuData[startindex:endindex][4:] # 正累计，4字节，float

        startindex = endindex
        endindex += 4+dataNum*8
        negtotalInt = rtuData[startindex:endindex][4:] # 反累计，4字节

        startindex = endindex
        endindex += 4+dataNum*8
        negtotalDec = rtuData[startindex:endindex][4:] # 反累计，4字节，float
        
        startindex = endindex
        endindex += 4+dataNum*8
        pressData = rtuData[startindex:endindex][4:] # 压力，4字节，float
        # print(pressData)
        
        startindex = endindex
        endindex += 4+dataNum*4
        statusData = rtuData[startindex:endindex][4:]   # 状态，2字节 
        
        # print(type(strtime))
        # print(strtime)
        readdateList = getPandasDateList(strtime, dataNum, str(readFlg) + 'min')
        retList1 = []
        retList2 = []
        for index in range(dataNum):
            postotal = int(postotalInt[index*8:(index + 1)*8],16)
            posDec = postotalDec[index*8:(index + 1)*8]
            posDec = float(hightolow(posDec,4))
            postotal += posDec
            
            negtotal = int(negtotalInt[index*8:(index + 1)*8],16)
            negDec = negtotalDec[index*8:(index + 1)*8]
            negDec = float(hightolow(negDec,4))
            negtotal += negDec
            
            insflow = insflowData[index*8:(index + 1)*8]
            insflow = float(hightolow(insflow,4))
            
            press = pressData[index*8:(index + 1)*8]
            # print(press)
            press = float(hightolow(press,4))
            
            outstr1 = "表号:{},".format(meterNum)
            outstr1 += '时间:{},'.format(readdateList[index])
            outstr1 += '正累计:{:.3f},'.format(postotal)
            outstr1 += '反累计:{:.3f},'.format(negtotal)
            outstr1 += '瞬时:{:.3f}(m3/h),'.format(insflow)
            outstr1 += '压力:{:.3f}/Mpa'.format(press)
            retList1.append(outstr1)
            # print(outstr1)
            
            outstr2 = '{},'.format(meterNum)
            outstr2 += '{},'.format(readdateList[index])
            outstr2 += '{:.3f},'.format(postotal)
            outstr2 += '{:.3f},'.format(negtotal)
            outstr2 += '{:.2f},'.format(insflow)
            outstr2 += '{:.3f},'.format(press)
            outstr2 += '{:.2f}'.format(0)
            retList2.append(outstr2) 
        return retList1,retList2,outstr3
    return None
        
def hex_to_asciiStr(hexStr):  # 把hex格式的字符串转换成ascii码类型的字符串
    # 4F4B -- > OK
    # 方法1
    # data = []
    # for i in range(0, len(hexStr), 2):
    #     data.append(chr(int(hexStr[i:i + 2], 16)))
    # data = ''.join(data)
    # 方法2
    data = a2b_hex(hexStr).decode()
    return data # 字符串类型
def ascii_to_hexStr(AsciiStr):  # 把字符串格式的ascii码转换成对应的16进制字符串
    # OK--> 4F4B
    # 方法1
    # data = []
    # for c in AsciiStr:
    #     data.append(str(hex(ord(c))[2:]))
    # data = ''.join(data).upper()
    # 方法2
    data = AsciiStr.encode().hex().upper()
    return data  
def ascii_to_string(ascii_to_str):  # 网络字节数据（即ASCII码）转换成字符串
    # data = b'\xaa\x96\x02\x12\x05\x00\x00\x01\x01\x02\x01\x01\x00\x00\x01' 转换成如下
    # data = "AA9602120500000101020101000001"
    return_data = b2a_hex(ascii_to_str).decode("utf-8")
    return return_data
def string_to_ascii(data):  # 字符串转换成网络字节数据（即ASCII码）
    # data = AA9602120500000101020101000001 转换成如下
    # b'\xaa\x96\x02\x12\x05\x00\x00\x01\x01\x02\x01\x01\x00\x00\x01'
    data = bytes.fromhex(data)
    return data # 字节类型    

# UDP发送数据函数
def sendDataToClientUDP(self, send_data):
    # 发送数据到客户端
    # log.logger.info("-->%s[thID:%s],%s:%s", self.thName, self.thIdent, mesg,send_data)
    send_data = string_to_ascii(send_data)
    self.socket.sendto(send_data, self.client_address)

def strFillFunction(inputStr, length):
    # 传入字符串，指定长度，在前面补0
    if len(inputStr) < length:
        inputStr = (length - len(inputStr))* '0' + inputStr
    
    return inputStr.upper()
    
def coapParse(byteData):
    # print(byteData)
    # print(type(byteData))
    
    # coap协议解析    
    if type(byteData).__name__ == 'bytes':
        pass
        # print(type(byteData))
    else:
        byteData = bytes.fromhex(byteData)
        # print(type(byteData).__name__)
    
    version = byteData[0] >> 6
    messageType = (byteData[0]>>4) & 3 # 0:CON, 1:NON, 2:ACK, 3:RESET
    tokenLen = byteData[0]  & 0xf

    # message types
    if messageType == 0:
        messageType = CON_TYPE
    elif messageType == 1:
        messageType = NON_TYPE
    elif messageType == 2:
        messageType = ACK_TYPE
    elif messageType == 3:
        messageType = RST_TYPE
    
    # method codes
    methodCode = byteData[1]
    if methodCode == 0:
        methodCode = METHOD_EMPTY
    elif methodCode == 1:
        methodCode = METHOD_GET
    elif methodCode == 2:
        methodCode = METHOD_POST
    elif methodCode == 3:
        methodCode = METHOD_PUT
    elif methodCode == 4:
        methodCode = METHOD_DELETE
    if methodCode == METHOD_POST:
        mid = (byteData[2]<<8) + byteData[3]
        mid = hex(mid)[2:]
        mid = strFillFunction(mid, 4)

        token = bytearray()
        for index in range(tokenLen):
            token.append(byteData[4+index])
        token = b2a_hex(token).decode().upper() # 字符串模式
        token = strFillFunction(token, 8)
        # print(type(token))
        
        tlvData = bytearray()
        for index in range(len(byteData) - 4 - tokenLen):
            tlvData.append(byteData[4+tokenLen + index])
        tlvData=tlvData.decode('utf-8','ignore') # 忽略非法字符，用strict会抛出异常,得到字符串模式

        # print(tlvData)
        if 'ep=' in tlvData:
            epIndex = tlvData.index('ep=') + 3
            imei = tlvData[epIndex:epIndex+15]
            imei = ascii_to_hexStr(imei)
            # print(type(imei))
        else:
            imei = None
        
        if ',<' in tlvData:
            epIndex = tlvData.index(',<') + 2
            objects = tlvData[epIndex:]
        else:
            objects = None
            
        clientInfo = dict()
        clientInfo['name'] = hex_to_asciiStr(imei)
        clientInfo['version'] = str(version) + '.0'
        clientInfo['mid'] = mid
        clientInfo['messageType'] = messageType
        clientInfo['methodCode'] = methodCode
        clientInfo['token'] = token
        clientInfo['objects'] = objects
        
        # print(type(clientInfo['mid']))
        # print(type(clientInfo['token']))
        # print(type(clientInfo['name']))

        # print('clinet info:{}'.format(clientInfo))
        print("\tname:       {}".format(clientInfo['name']))
        print("\tversion:    {}".format(clientInfo['version']))
        print("\tmid:        {}".format(clientInfo['mid'].upper()))
        print("\tmessageType:{}".format(clientInfo['messageType']))
        print("\tmethodCode: {}".format(clientInfo['methodCode']))
        print("\ttoken:      {}".format(clientInfo['token']))
        print("\tobjects:    {}".format(clientInfo['objects']))
        return clientInfo
    
def BcdToHexStr(charVal):  # BCD类型转换成hex的字符串
    charStr = str(charVal)
    if len(charStr) < 2:
        charStr = "0" + charStr
    return charStr.upper()
    
def calSum(ValString):  # 包头供水、江夏水务,徐州首创水务
    # print(ValString)
    strHex = bytes.fromhex(ValString)
    sum = 0
    for i in range(len(strHex)):
        sum += strHex[i]

    sum = sum & 0xFF
    sum = hex(sum)[2:]
    if len(sum) < 2:
        sum = "0" + sum
    sumHex = sum
    return sumHex.upper() # 返回的是16进制

def stringToLen(valString,mode):
    # 计算一个字符串的长度并返回长度的低字节和高字节，字符形式
    # 例如：'1234567895'-->转换成：len(1234567895)/2,返回0500
    
    len1 = int(len(valString) / 2)        
    len_low = hex(len1 & 0x00FF)[2:]
    if len(len_low) < 2:
        len_low = "0" + len_low

    len_high = hex(((len1 & 0xFF00) >> 8))[2:]
    if len(len_high) < 2:
        len_high = "0" + len_high
        
    if mode == 1: # 返回低字节+高字节，分开
        rtStr = len_low, len_high            
    elif mode =='L': # 返回，低字节在前：合并在一起
        rtStr = len_low + len_high
    elif mode == 'H':# 返回，高字节在前：合并在一起
        rtStr = len_high + len_low
    
    if mode == 1:
        return rtStr
    else:
        return rtStr.upper()
            
# -------------------------------------------------------------
# 以下是包头水司，和达协议开始
class BaoTouProtoHandle(object):
    def __init__(self):
        pass
    
    @staticmethod
    def GetPackage(funcode,recv_data):
        cmd = "F88F"  # 帧头
        cmd += recv_data[4:4+16].upper()  # 设备地址
        
        cmd += "0102"  # 厂商代码
        cmd += "01"  # 协议版本

        time_str = time.localtime()
        cmd += BcdToHexStr(time_str.tm_year % 100)  # 时间BCD格式
        cmd += BcdToHexStr(time_str.tm_mon)
        cmd += BcdToHexStr(time_str.tm_mday)
        cmd += BcdToHexStr(time_str.tm_hour)
        cmd += BcdToHexStr(time_str.tm_min)
        cmd += BcdToHexStr(time_str.tm_sec)

        cmd += funcode  # 设备注册回复帧
        cmd += "0001"  # 帧流水号
        cmd += "00"  # 加密方式，0：不加密，1：加密，AES128
        cmd += "0102"  # 秘钥版本
        cmd += "00"  # 压缩方式，0：不压缩，1：压缩GZIp
        cmd += "0000"  # 保留字段
        return cmd.upper()

    def logindata(recv_data):  # 和达协议，设备注册回复帧
        cmd = BaoTouProtoHandle.GetPackage("81", recv_data)

        tag = "81"  # 一级tag，功能码，设备注册的回复帧

        # 二级tag
        tag1 = "01"  # 终端序列号
        tag1 += "0001"  # 长度
        tag1 += "00"  # value：注册成功

        tag2 = "02"  # 加密方式
        tag2 += "0001"  # 长度
        tag2 += "00"  # value：不加密

        tag3 = "03"  # 密钥
        tag3 += "0012"  # 长度
        tag3 += "0101"  # value:版本
        tag3 += "0102030405060708090A0B0C0D0E0F0A"  # 密钥

        tag4 = "04"  # 压缩方式
        tag4 += "0001"  # 长度
        tag4 += "00"  # value：不压缩

        # 一级tag的长度
        tagLen_low, tagLen_high = stringToLen(tag1 + tag2 + tag3 + tag4, 1) 
        tag += tagLen_high + tagLen_low + tag1 + tag2 + tag3 + tag4

        tagLen_low, tagLen_high = stringToLen(tag, 1)

        # 数据域长度
        DataLen_low, DataLen_high = stringToLen(tag + tagLen_low + tagLen_high, 1)

        cmd += DataLen_high + DataLen_low + tagLen_high + tagLen_low + tag

        cmd_cs = calSum(cmd)

        cmd += cmd_cs + "16"
        return cmd.upper()

    def realDataresponse(recv_data):  # 和达协议，发送RTU数据的回复帧
        cmd = BaoTouProtoHandle.GetPackage("82", recv_data)
        tag = "0007"
        tag += "0005"
        tag += "82"  # 一级tag，功能码，设备注册的回复帧
        tag += "0002"
        tag += "0000"
        cmd += tag
        cmd_cs = calSum(cmd)
        cmd += cmd_cs + "16"
        return cmd.upper()

# from ctypes import c_char_p
# import ctypes
# from multiprocessing import Manager, Value
# import multiprocessing
# shared_var = multiprocessing.Value('s','')
# shared_var = Value('i', 0) # i代表是整形变量
# shared_var = Manager().Value(ctypes.c_char_p, 'hello') # c_char_p代表是字符串
# manager = Manager()
# imeiInfoAndClientOject = manager.dict()
# imeiInfoAndClientOject['client'] = 0
# imeiInfoAndClientOject['imei'] = 0
# with shared_var.get_lock():
#     print('Current variable value={}'.format(shared_var.value))

# with imeiInfoAndClientOject:
#     print("imei and client = {}".format(imeiInfoAndClientOject))

class UdpMultSockServer(socketserver.BaseRequestHandler):
    # global thName
    # global thIdent
    
    def setup(self): # # 1.每一个连接初始化  
        # super.setup()
        # self.request.settimeout(120)  # 超时时间，默认120秒
        
        # self.event = threading.Event()
        # with self.lock:
        #     self.clients[self.client_address] = self.request
        
        # lock = threading.Lock()
            
        self.thName = threading.currentThread().name # 线程名称
        self.thIdent = threading.currentThread().ident # 线程ID 
        client_address = self.client_address  # 客户端地址
        log.logger.info("-->[%s,%s],ConnFrom:%s", self.thName, self.thIdent, client_address)

        # print('self.clients = {}'.format(self.clients))
        # super().handle()
        # data   = self.request[0].strip() # 方式1
        # socket = self.request[1]        
        # recv_data,socket = self.request # 方式2
        # self.client_address = self.client_address # 客户端连接过来的地址
        
        # print(len(self.request))
        # print(self.request[0])
        # print(type(self.request[1]))
                    
    def handle(self):        
        # with self.lock:   # 锁定在一个线程          
        recv_data = self.request[0].strip()
        self.socket = self.request[1]
        
        # recvdata = bytes.decode(self.request[0])   # 字节模式转换成字符串              
        recvDataStrType = ascii_to_string(recv_data).upper() # 转换为字符串模式
        
        # localTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())             
        log.logger.info("-->[%s,%s],RecvData:%s", self.thName, self.thIdent, recvDataStrType)

        codeType = recv_data[1] 
        # message_id = random.randint(1, 1024)
        # message_id = hex(message_id)[2:]
        # if len(message_id) < 4:
        #     message_id = (4 - len(message_id)) * '0' + message_id
        # print(message_id)
        
        frameHead1 = recv_data[0] 
        frameHead2 = recv_data[1]
        
        # if frameHead1 == 0xF8 and frameHead2 == 0x8F:
        if 0: # 不处理UDP发送来的定制协议
            funCode = recvDataStrType[38:40]  # 定制协议中的功能码        
            
            if funCode == '01':  # 代表设备注册报文
                recv_data = recvDataStrType
                cmd = BaoTouProtoHandle.logindata(recv_data)
                log.logger.info("-->[%s,%s],send login response:%s", self.thName, self.thIdent, cmd)
                sendDataToClientUDP(self, cmd)  # 下发注册请求的报文
            elif funCode == '02':
                recv_data = recvDataStrType
                cmd = BaoTouProtoHandle.realDataresponse(recv_data)
                log.logger.info("-->[%s,%s],send flow data response:%s", self.thName, self.thIdent, cmd)
                sendDataToClientUDP(self, cmd)  # 下发数据上报的报文
                retList = BaoTouShuiwuProto(recv_data)
                if retList != None:
                    for index,value in enumerate(retList[0]):
                        log.logger.info("-->[%s,%s]:%s", self.thName, self.thIdent, value)
                    
                    # sqliteHandle("./meterFlow_udp.db", retList[1])# 写入数据库
                
        else:  # coap + udp        
            # 表示客户端发送注册请求中有rd
            # 有ep=xxxxx，xxx是IMEI号,65703d：ep=
            if '65703D' in recvDataStrType:        
                clientInfo = coapParse(recvDataStrType)
                
                cmd = '64'
                cmd += '41'  # response codes:  0x41:Created
                cmd += clientInfo['mid']
                cmd += clientInfo['token']
                cmd += '827264'  # option：的内容为rd
                cmd += '0d02' + ascii_to_hexStr(clientInfo['name'])
                cmd = cmd.upper()
                clientMsg = 'imei:' + clientInfo['name'] + ',objects:' + clientInfo['objects']
                log.logger.info("-->[%s,%s],client info[%s]", self.thName, self.thIdent, clientMsg) 
                            
                # 第一步：cmd为收到客户端发送到注册请求后，服务区回复客户端
                localTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
                log.logger.info("-->[%s,%s],send register /rd response:%s", self.thName, self.thIdent, cmd)
                sendDataToClientUDP(self, cmd)
                
                # 延时1秒钟，服务器端发起注册请求
                time.sleep(1)
                
                # 模组订阅
                # 第一发送：/3/0
                cmd = '44'
                cmd += '01'  # get type
                mid = hex(int(clientInfo['mid'], 16) + 1)[2:]       
                cmd += strFillFunction(mid, 4)
                
                token = hex(int(clientInfo['token'], 16) + 1)[2:]
                cmd += strFillFunction(token, 8)
                cmd += '6100'
                cmd += '5133'  # /3
                cmd += '0130'  # /0
                cmd = cmd.upper()  
                
                log.logger.info("-->[%s,%s],send subscribe /3/0 request:%s", self.thName, self.thIdent, cmd)
                sendDataToClientUDP(self, cmd)
            
            if codeType == 0x45 and '42433935' in recvDataStrType: # Response codes : 2.05(0x45), Content        
                # 42433935代表是发送的/3/0 request中的模组型号BC95可以提取中相关信息，这里没有提取
                # 紧接着发送/19/0/0
                cmd = '44'
                cmd += '01'  # get type
                cmd += '1234'
                cmd += '12345678'
                cmd += '6100'
                cmd += '523139'  # /19
                cmd += '0130'  # /0
                cmd += '0130'  # /0
                cmd = cmd.upper()
                
                log.logger.info("-->[%s,%s],send subscribe /19/0/0 request:%s", self.thName, self.thIdent, cmd)
                sendDataToClientUDP(self, cmd)

            if codeType == 0x45 and (len(recvDataStrType) > 126): # 代表定制协议
                # 第三发送：服务器要回复设备上报的/19/0/0
                cmd = '60'
                cmd += '00'  # empty message
                cmd += recvDataStrType[4:4+4]
                cmd = cmd.upper()

                log.logger.info("-->[%s,%s],send subscribe /19/0/0 response:%s", self.thName, self.thIdent, cmd)
                sendDataToClientUDP(self, cmd)  # 此处回复的是NON消息，告知设备我已收到你的请求
                
                funCode = recvDataStrType[64:66]  # 定制协议中的功能码
                if funCode == '01':  # 代表设备注册报文
                    # 这里要根据具体的定制协议在FF后面添加上具体报文                        
                    # 第四步服务器要发送/19/1/0:代表指令，例如携带定制协议的报文
                    cmd = '44'
                    cmd += '03'  # PUT模式            
                    mid = hex(int(recvDataStrType[4:4+4], 16) + 1)[2:]
                    mid = strFillFunction(mid, 4)
                    cmd += mid
                    cmd += '12345678'  # TOKEN
                    cmd += 'B23139'  # /19
                    cmd += '0131'  # /1
                    cmd += '0130'  # /0
                    cmd += '112A'
                    cmd += 'FF'
                    recv_data = recvDataStrType[26:]
                    # 此报文中有IMEI号
                    # 4445FFDA123456786101612AFF
                    # F88F00300170791000200030002312080932060100500000000000000031002F01002C01000800010000999990030200080460047414304283030008
                    # 0869846060838741
                    # 0400020101050003000D024516
                                        
                    imei = recv_data[120:120+16]
                    txtDir = './txt/'
                    if not os.path.exists(txtDir):
                        os.mkdir(txtDir)
                    logFilename = txtDir + str(self.client_address[0]) + '-' + str(self.client_address[1]) +'.txt'
                    with open(logFilename, 'w') as fp:
                        fp.write(imei)
                    
                    cmd += BaoTouProtoHandle.logindata(recv_data)                 
                    log.logger.info("-->[%s,%s],send cmd /19/1/0 response:[imei:%s],%s", self.thName, self.thIdent, imei, cmd)
                    sendDataToClientUDP(self, cmd)  # 下发注册请求的报文
                elif funCode == '02':
                    cmd = '44'
                    cmd += '03'  # PUT模式            
                    mid = hex(int(recvDataStrType[4:4+4], 16) + 1)[2:]
                    mid = strFillFunction(mid, 4)
                    cmd += mid
                    cmd += '12345678'  # TOKEN
                    cmd += 'B23139'  # /19
                    cmd += '0131'  # /1
                    cmd += '0130'  # /0
                    cmd += '112A'
                    cmd += 'FF'
                    recv_data = recvDataStrType[26:]
                    txtDir = './txt/'
                    if not os.path.exists(txtDir):
                        os.mkdir(txtDir)
                    logFilename = txtDir + str(self.client_address[0]) + '-' + str(self.client_address[1]) +'.txt'
                    try:
                        with open(logFilename, 'r') as fp: # 已只读的方式打开
                            imei = fp.readline()

                        cmd += BaoTouProtoHandle.realDataresponse(recv_data)
                        log.logger.info("-->[%s,%s],send subscribe /19/1/0 response:%s", self.thName, self.thIdent, cmd)
                        sendDataToClientUDP(self, cmd)  # 下发数据上报的报文
                        retList = BaoTouShuiwuProto(recv_data)
                        if retList != None:
                            for index,value in enumerate(retList[0]):
                                log.logger.info("-->[%s,%s]:[imei:%s],%s", self.thName, self.thIdent, imei,value)
                            
                            # 根据IMEI号来创建数据库文件
                            sqliteHandle("./meterFlow_lwm2m.db", retList[1],imei)# 写入数据库
                    except Exception as e:
                        log.logger.info("-->[%s,%s],%s", self.thName, self.thIdent, e)
    def finsh(self):  # 3.每一个连接的清理
        print("close connect {}".format(self.client_address))
        # self.event.set()
        # with self.lock:
        #     if self.client_address in self.clients:
        #         self.clients.pop(self.client_address)
        #     self.request.close()
        # finally:
        #     print('close connect'.format(self.client_address))
        #     self.socket.close()            
        
def Thread2UdpServer(log, address):     
    log.logger.info("-->Starting up on (%s:%s) UdpServer", address[0], address[1])
    # UdpMultSockServer：是异步的，可以同时处理多个连接   
    s = socketserver.ThreadingUDPServer(address, UdpMultSockServer)  # UDP  

    s.allow_reuse_address = True # 允许重用地址
    s.serve_forever()  # 持续接受客户端的连接
    
    # s.request_queue_size # 请求队列的大小
    # s.timeout = 120 # request请求超时时间
    # s.shutdown() # 告诉server_forever循环停止
    # s.server_close() # 关闭服务器


if __name__ == "__main__":
    filePath = os.path.dirname(os.path.realpath(__file__))  # 当前可执行文件的路径
    logDir = './log/'
    if not os.path.exists(logDir):
        os.mkdir(logDir)
    filePath = logDir + "/serverLog.log"
    log = Logger(filePath, level="debug")

    # 服务器监听的本地IP地址和端口
    addrress = (
        ('0.0.0.0',5683),  
        # ('192.168.3.99',5683),
    )

    if 1:  # udpServer       
        threadPool = []  # 线程列表

        # print('thread2_1 UdpServer') # SakufrpNgrok UDP对应的本地19132-->31200
        th = threading.Thread(name='threadUDP', target=Thread2UdpServer, args=(log, addrress[0]))
        threadPool.append(th)  

        for index,value in enumerate(threadPool): 
            value.start() # 批量启动线程  
    else:
        data = '4402011A1A010900B272641128396C776D326D3D312E300D0565703D38363938343630363038333132333403623D55086C743D3836343030FF3C2F3E3B72743D226F6D612E6C776D326D222C3C2F312F303E2C3C2F323E2C3C2F332F303E2C3C2F342F303E2C3C2F352F303E2C3C2F362F303E2C3C2F31392F303E2C3C2F31392F313E2C3C2F333335312F303E'      
        data = '4402dd6f6fdd0400b272641128396c776d326d3d312e300d0565703d38363138393730363833393039303203623d55096c743d383634303030ff3c2f3e3b72743d226f6d612e6c776d326d223b63743d31313534332c3c2f312f303e2c3c2f332f303e2c3c2f342f303e2c3c2f352f303e2c3c2f31392f303e2c3c2f31392f313e'
        coapParse(data) # coap协议解析        